﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using NUnit.Framework;

namespace memcached
{
    /// <summary>
    /// Test the in-proc mem-only Cache.
    /// </summary>
    [TestFixture]
    public class MemCacheMockTest : MemCacheTestBaseExt
    {
        protected override ICache GetCache()
        {
            return new MemCacheMock();
        }
    }

    /// <summary>
    /// Test the real memcached intefrace.
    /// </summary>
    [TestFixture]
    public class MemCacheTest : MemCacheTestBaseExt
    {
        protected override ICache GetCache()
        {
            // note, the timeouts are made up and may require adjustment or super-machines
            return new MemCache();
        }

        [Test]
        public void PerformCrossProcess()
        {
            string key = GetKey();
            bool dist = DistMemCache.PerformCrossProcess(1000, key, () =>
            {
                bool res = DistMemCache.PerformCrossProcess(1000, key, () =>
                {
                    Assert.Fail();
                });
                Assert.IsFalse(res);
            });
            Assert.IsTrue(dist);

            bool executed = false;
            dist = DistMemCache.PerformCrossProcess(1000, key, () =>
            {
                executed = true;
            });
            Assert.IsTrue(dist);
            Assert.IsTrue(executed);
        }

        [Test]
        public void GetLockFor()
        {
            string key = GetKey();
            var r = SyncMemCache.GetLockFor(key);
            Assert.IsTrue(r.CurrentReadCount == 0);

            SyncMemCache.ClearLocks();
        }

        [Test]
        public void SyncMemCacheTest()
        {
            string key = GetKey();
            var c = GetCache();
            using (var smc = new SyncMemCache(c))
            {
                bool res = smc.Set(key, (k) => "x");
                Assert.IsTrue(res);
                string x = smc.Get<string>(key, out res);
                Assert.IsTrue(res);
                Assert.AreEqual(x, "x");
            }
        }

        [Test]
        public void Pool()
        {
            var c = GetCache();
            var p = new CachePool(1, () => c);
            Assert.IsTrue(p.MaximumSize == 1);
            var c1 = p.Acquire(1);
            Assert.IsNotNull(c1);
            Assert.AreEqual(c, c1);
            Assert.IsTrue(p.CurrentSize == 1);
            var c2 = p.Acquire(10);
            Assert.IsNull(c2);
            p.Release(c1);
            Assert.IsTrue(p.CurrentSize == 1);
            p.Dispose();
        }

        [Test]
        public void PerformOnce()
        {
            string key = GetKey();
            ICache m = GetCache();
            bool res = DistMemCache.PerformOnce(m, 1, key, () =>
            {
                bool r = DistMemCache.PerformOnce(m, 1, key, () =>
                {
                    Assert.Fail();
                });
                Assert.IsFalse(r);
            });
            Assert.IsTrue(res);
        }
    }

    /// <summary>
    /// Test the real NCache intefrace.
    /// NCache will fail expiry tests. It requires <![CDATA[<cleanup interval="1sec"/>]]> to work properly.
    /// </summary>
    [TestFixture]
    public class NCacheTest : MemCacheTestBase
    {
        protected override ICache GetCache()
        {
            // note, the timeouts are made up and may require adjustment or super-machines
            return new NCache();
        }

        protected override int Wait
        {
            get { return 2500; }
        }
    }

    /// <summary>
    /// Test for Redis store.
    /// </summary>
    [TestFixture]
    public class RedisTest : MemCacheTestBaseExt
    {
        protected override ICache GetCache()
        {
            return new RedisCache();
        }

        protected override int Wait
        {
            get { return 2000; }
        }
    }

    /// <summary>
    /// Test the transactional version.
    /// </summary>
    [TestFixture]
    public class TxCacheReadCommittedTest
    {
        public void Run()
        {
            TxCommit();
            TxRollback();
            try
            {
                TxFail();
            }
            catch (System.Transactions.TransactionAbortedException) { }
        }

        [Test]
        public void TxCommit()
        {
            var to = new System.Transactions.TransactionOptions();
            to.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
            using (var ts = new System.Transactions.TransactionScope(
                 System.Transactions.TransactionScopeOption.Required, to))
            {
                new TxRunner().Run();
                ts.Complete();
            }
        }

        [Test]
        public void TxRollback()
        {
            var to = new System.Transactions.TransactionOptions();
            to.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
            using (var ts = new System.Transactions.TransactionScope(
                 System.Transactions.TransactionScopeOption.Required, to))
            {
                new TxRunner().Run();
            }
        }

        [Test]
        [ExpectedException(typeof(System.Transactions.TransactionAbortedException))]
        public void TxFail()
        {
            var to = new System.Transactions.TransactionOptions();
            to.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
            to.Timeout = TimeSpan.FromSeconds(1);
            using (var ts = new System.Transactions.TransactionScope(
                 System.Transactions.TransactionScopeOption.Required, to))
            {
                try
                {
                    new TxRunner().Run();
                }
                catch (System.Reflection.TargetInvocationException e)
                {
                    // when Tx fails, current operation will also fail
                    if (!(e.InnerException is NUnit.Framework.AssertionException))
                        throw;
                }
                ts.Complete();

                var tx = System.Transactions.Transaction.Current;// should fail
                Assert.Fail();
            }
            Assert.Fail();
        }

        private sealed class TxRunner : MemCacheTestBase
        {
            internal TxRunner()
            {
            }

            protected override ICache GetCache()
            {
                return new TxCache(new MemCache());
            }
        }
    }

    [TestFixture]
    public class ReadAhreadTest
    {
        [Test]
        public void TestFetcher()
        {
            var s = new MemCache();
            var ra = new ReadAheadCache(s);
            var k = GetKey();

            ra.Register(k, (store, key) =>
            {
                return "value";
            });
            ra.Start();

            bool res;
            // no value yet
            var v = ra.Get(k, out res);
            Assert.IsTrue(res);
            Assert.IsNull(v);

            // add with expiry
            res = ra.Add(k, 1, "value1");
            Assert.IsTrue(res);

            v = ra.Get(k, out res);
            Assert.IsTrue(res);
            Assert.IsNotNull(v);

            System.Threading.Thread.Sleep(2000);
            // now we should get a refreshed item
            v = ra.Get(k, out res);
            Assert.IsTrue(res);
            Assert.IsNotNull(v);
            Assert.AreEqual("value", v);

            System.Threading.Thread.Sleep(2000);
            // and again
            v = ra.Get(k, out res);
            Assert.IsTrue(res);
            Assert.IsNotNull(v);
            Assert.AreEqual("value", v);

            int d = ra.GetAvgDuration(k);
            Assert.IsTrue(d > 0);

            ra.Dispose();
        }

        protected static string GetKey()
        {
            return (DateTime.Now.Ticks + key++).ToString();
        }
        private static int key;
    }
}